#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#include <sys/signal.h>

int stop_flag = 0;
int debug = 0;
int first_end = 1;

#define RUN_TIME 2

#define FD_OFFSET 10

void timeout(int sig)
{
    stop_flag = 1;
}

int select_calls(int num, int fd_arr[])
{
    int count = 0;
    int i= 0;
    int ret = 0;
    fd_set wset;
    fd_set w;
    int maxfd = 0;

#define MAX(a, b)  (a)>(b) ? (a) : (b)

    FD_ZERO(&wset);
    if (first_end)
    {
        FD_SET(fd_arr[0], &wset);
        maxfd = MAX(maxfd, fd_arr[0]);
        FD_SET(fd_arr[num-1], &wset);
        maxfd = MAX(maxfd, fd_arr[num-1]);
    }
    else
        for (; i<num; i++)
        {
            FD_SET(fd_arr[i], &wset);
            maxfd = MAX(maxfd, fd_arr[i]);
        }

    stop_flag = 0;
    alarm(RUN_TIME);
    while(!stop_flag)
    {
        w = wset;
        if (-1 == (ret=select(maxfd+1, NULL, &w, NULL, NULL)))
        {
            if (EINTR != errno)
            {
                fprintf(stderr, "%s fail: %d %s\n", __func__, errno, strerror(errno));
                return -1;
            }
        }
        else if (ret > 0)
        {
            count++;
        }
        if (debug)
        {
            if (ret != num)
            {
                fprintf(stderr, "ret!=num ret=%d\n", ret);
            }
            else
                for (i=0; i<num; i++)
                {
                    assert(FD_ISSET(fd_arr[i], &w));
                }
        }
    }
    return count;
}

int poll_calls(int num, int fd_arr[])
{
    int count = 0;
    int i= 0;
    int ret = 0;
    struct pollfd* pfd_arr = calloc(num, sizeof(struct pollfd));

    if (first_end)
    {
        for (i=0; i<num; i++)
        {
            pfd_arr[i].fd = -1;
        }
        i = 0;
        pfd_arr[i].fd = fd_arr[i];
        pfd_arr[i].events = POLLOUT;
        i = num-1;
        pfd_arr[i].fd = fd_arr[i];
        pfd_arr[i].events = POLLOUT;
    }
    else
        for (i=0; i<num; i++)
        {
            pfd_arr[i].fd = fd_arr[i];
            pfd_arr[i].events = POLLOUT;
        }

    stop_flag = 0;
    alarm(RUN_TIME);
    while(!stop_flag)
    {
        if (-1 == (ret=poll(pfd_arr, num, 0)))
        {
            if (EINTR != errno)
            {
                fprintf(stderr, "%s fail: %d %s\n", __func__, errno, strerror(errno));
                return -1;
            }
        }
        else if (ret > 0)
        {
            count++;
        }
        if (debug)
        {
            if (ret != num)
            {
                fprintf(stderr, "ret!=num ret=%d\n", ret);
            }
            else
                for (i=0; i<num; i++)
                {
                    assert(pfd_arr[i].revents & POLLOUT);
                }
        }
    }
    return count;
}

int epoll_calls(int num, int fd_arr[])
{
    int count = 0;
    int i= 0;
    int ret = 0;
    int epfd;
    struct epoll_event event;
    struct epoll_event* ev_arr = calloc(num, sizeof(struct epoll_event));

    epfd = epoll_create(num+1);
    if (first_end)
    {
        i = 0;
        event.events = EPOLLOUT;
        event.data.u32 = i;
        ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd_arr[i], &event);
        assert(ret == 0);

        i = num-1;
        event.events = EPOLLOUT;
        event.data.u32 = i;
        ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd_arr[i], &event);
        assert(ret == 0);
    }
    else
        for (; i<num; i++)
        {
            event.events = EPOLLOUT;
            event.data.u32 = i;
            ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd_arr[i], &event);
            if (ret == -1)
            {
                fprintf(stderr, "epoll_ctl fail:fd=%d errno=%d str=%s\n", fd_arr[i], errno, strerror(errno));
                return -1;
            }
        }

    stop_flag = 0;
    alarm(RUN_TIME);
    while(!stop_flag)
    {
        if (-1 == (ret=epoll_wait(epfd, ev_arr, num, -1)))
        {
            if (EINTR != errno)
            {
                fprintf(stderr, "%s fail: %d %s\n", __func__, errno, strerror(errno));
                return -1;
            }
        }
        else if (ret > 0)
        {
            count++;
        }
        if (debug)
        {
            if (ret != num)
            {
                fprintf(stderr, "ret!=num ret=%d\n", ret);
            }
            else
                for (i=0; i<ret; i++)
                {
                    assert(ev_arr[i].events & EPOLLOUT);
                }
        }
    }
    return count;
}

int main(int argc, char* argv[])
{
    int count;
    int num_fd = 0;
    int* fd_arr = 0;
    int i = 0;
    int rw_fds[2];

    if (argc < 2)
    {
        fprintf(stderr, "usage: %s nums\n", argv[0]);
        return 1;
    }
    num_fd = atoi(argv[1]);
    if (num_fd < 1)
    {
        fprintf(stderr, "maxfd too short!\n");
        return 1;
    }
    if (argc >= 3)
    {
        debug = atoi(argv[2]);
    }
    fd_arr = calloc(num_fd, sizeof(int));

    if (-1 == pipe(rw_fds))
    {
        fprintf(stderr, "pipe fail: no=%d str=%s\n", errno, strerror(errno));
        return 1;
    }
    for (i=0; i<num_fd; i++)
    {
        int fd = i + FD_OFFSET;
        if (dup2(rw_fds[1], fd) != fd)
        {
            fprintf(stderr, "dup2 error: fd=%d, err=%d, str=%s\n", fd, errno, strerror(errno));
            exit(1);
        }
        fd_arr[i] = fd;
    }

    signal(SIGALRM, timeout);

    count = select_calls(num_fd, fd_arr);
    fprintf(stderr, "select() calls per second: %10d\n", count/RUN_TIME);

    count = poll_calls(num_fd, fd_arr);
    fprintf(stderr, "poll() calls per second:   %10d\n", count/RUN_TIME);

    count = epoll_calls(num_fd, fd_arr);
    fprintf(stderr, "epoll() calls per second:  %10d\n", count/RUN_TIME);

    return 0;
}


